﻿#region Usings

using System;
using System.Collections.Generic;
using System.Threading;
using RoMBot.MemoryLib;
using System.Linq;
using RoMBot.Properties;

#endregion

namespace RoMBot.Classes
{
    public enum ItemType
    {
        Weapon,
        Armor,
        Supplies,
        Materials,
        Recipes,
        EquipmentEnhancement,
        MonsterCard,
        Mounts,
        DomesticObject,
        SpecialObject,
        MisionObject,
        Others,
        Money
    }

    public class Item
    {
        protected const string ItemLinkFormat = "|Hitem:{0}|h|c{1}[{2}]|r|h";
        public const string EmptyName = "<EMPTY>";
        private const string EmptyItemLink = "|Hitem:33BF1|h|cff0000ff[Empty]|r|h";
        public const int ItemReuseDelay = 1;
        public const int ItemFlagItemshopitemMask = 0X4;
        public const int ItemFlagCanbesoldMask = 0X200;
        public const int ItemFlagNotDropedOnPkDeathMask = 0X2; // noted for possible future use
        private const int InventoryItemSize = 68;

        internal static readonly uint[] Itemcolor = new uint[]
                                                     {
                                                             0xFFFFFFFF,
                                                             0xFF00FF00,
                                                             0xFF0072BC,
                                                             0xFFA864A8,
                                                             0xFFF68E56,
                                                             0xFFA37D50,
                                                             0,
                                                             0,
                                                             0xFFA864A8
                                                     };

        public Item()
        {
            this.Stats = new List<ItemEffect>();
            this.Runes = new List<ItemEffect>();
        }

        public Item( int slotNumber )
            : this()
        {
            this.Address = Addresses.staticInventory + ( slotNumber - 61 ) * InventoryItemSize;
            if ( this.Address == 0 )
            {
                return;
            }
            this.Update();
        }

        public virtual void UpdateViejo()
        {
            if ( this.Address == 0 )
            {
                return;
            }

            var id1 = this.Id;
            this.Id = Memory.ReadInt( this.Address );
            if ( this.Id != id1 && this.Id != 0 )
            {
                this.BaseItemAddress = MemoryTable.GetItemAddress( this.Id );
                if ( this.BaseItemAddress == 0 )
                {
                    return;
                }
                this.Name = "";
                this.Empty = false;
                this.ItemCount = Memory.ReadInt( this.Address + Addresses.itemCountOffset );
                this.Durability = Memory.ReadInt( this.Address + Addresses.durabilityOffset );
                if ( this.Durability > 0 )
                {
                    this.Durability = this.Durability / 100;
                }
                this.Value = Memory.ReadInt( this.BaseItemAddress + Addresses.valueOffset );
                if ( this.Value > 0 )
                {
                    this.Worth = this.Value / 10;
                }
                this.InUse = Memory.ReadInt( this.Address + Addresses.inUseOffset ) != 0;
                this.BoundStatus = Memory.ReadInt( this.Address + Addresses.boundStatusOffset );
                this.RequiredLvl = Memory.ReadInt( this.BaseItemAddress + Addresses.requiredLevelOffset );
                this.MaxStack = Memory.ReadInt( this.BaseItemAddress + Addresses.maxStackOffset );
                this.ObjType = Memory.ReadInt( this.BaseItemAddress + Addresses.typeOffset );
                if ( this.ObjType == 2 )
                {
                    var id2 = Memory.ReadInt( this.BaseItemAddress + Addresses.realItemIdOffset );
                    if ( id2 != 0 )
                    {
                        var itemAddress = MemoryTable.GetItemAddress( id2 );
                        if ( itemAddress != 0 )
                        {
                            this.CoolDownTime = Memory.ReadInt( itemAddress + Addresses.coolDownOffset );
                        }
                    }
                }
                int ptr;
                if ( this.Id >= 770000 && this.Id <= 771000 )
                {
                    ptr = Memory.ReadInt( MemoryTable.GetItemAddress( Memory.ReadInt( this.BaseItemAddress + Addresses.idCardNPCOffset ) ) + Addresses.nameOffset );
                    this.Name = "Card - ";
                }
                else if ( this.Id >= 550000 && this.Id <= 552000 )
                {
                    // We need to get info from recipe...
                    ptr = Memory.ReadInt( this.BaseItemAddress + Addresses.idRecipeItemOffset );
                    ptr = MemoryTable.GetItemAddress( ptr );
                    if ( ptr > 0 )
                    {
                        ptr = ( int ) Memory.ReadUInt( ptr + Addresses.nameOffset );
                    }
                    this.Name = "Recipe - "; // We should add a string so we can localize this
                }
                else
                {
                    ptr = Memory.ReadInt( this.BaseItemAddress + Addresses.nameOffset );
                }

                var str2 = ptr != 0 ? Memory.ReadASCIIString( ptr ) : string.Empty;
                this.Name = string.IsNullOrWhiteSpace( str2 ) ? "<EMPTY>" : this.Name + str2;
                this.Quality = Memory.ReadInt( this.BaseItemAddress + Addresses.qualityBaseOffset );
                var num1 = ( int ) Math.Truncate( Memory.ReadByte( this.Address + Addresses.qualityTierOffset ) / new Decimal( 16 ) );
                if ( num1 > 0 )
                {
                    this.Quality = this.Quality + num1 / 2;
                }
                var obj = this;
                var num2 = this.Id;
                var str3 = num2.ToString( "x8" );
                num2 = this.Color;
                var str4 = num2.ToString( "x8" );
                var str5 = string.Format( ItemLinkFormat, str3, str4, this.Name );
                obj.ItemLink = str5;
            }
            else if ( this.Id == 0 )
            {
                this.Empty = true;
                this.Id = 0;
                this.Name = "<EMPTY>";
                this.ItemCount = 0;
                this.Color = 16777215;
                this.SlotNumber = -1;
                this.Icon = "";
                this.ItemLink = "|Hitem:33BF1|h|cff0000ff[Empty]|r|h";
                this.Durability = 0;
                this.Quality = 0;
                this.Value = 0;
                this.Worth = 0;
                this.InUse = false;
                this.RequiredLvl = 0;
            }
            else
            {
                this.ItemCount = Memory.ReadInt( this.Address + Addresses.itemCountOffset );
                this.Durability = Memory.ReadInt( this.Address + Addresses.durabilityOffset );
                if ( this.Durability > 0 )
                {
                    this.Durability = this.Durability / 100;
                }
                this.InUse = Memory.ReadInt( this.Address + Addresses.inUseOffset ) != 0;
                this.BoundStatus = Memory.ReadInt( this.Address + Addresses.boundStatusOffset );
            }
        }

        public virtual void Update()
        {
            var nameAddress = 0;
            var oldId = this.Id;

            this.Id = Memory.ReadInt( this.Address );

            if ( this.Id != oldId && this.Id != 0 )
            {
                this.BaseItemAddress = MemoryTable.GetItemAddress( this.Id );
                if ( this.BaseItemAddress == 0 )
                {
                    Bot.Printf( System.Drawing.Color.Yellow, "Wrong value returned in update of item id: {0}", this.Id );
                    //logMessage(sprintf("Wrong value returned in update of item id: %d", this.Id));
                    return;
                }

                this.Name = "{0}";
                this.Empty = false;
                this.ItemCount = Memory.ReadInt( this.Address + Addresses.itemCountOffset );
                this.Durability = Memory.ReadInt( this.Address + Addresses.durabilityOffset );
                this.MaxDurability = Memory.ReadByte( this.Address + Addresses.maxDurabilityOffset );
                if ( this.Durability > 0 )
                {
                    this.Durability = this.Durability / 100;
                }
                this.Value = Memory.ReadInt( this.BaseItemAddress + Addresses.valueOffset );
                this.Worth = this.Value / 10;
                this.InUse = Memory.ReadInt( this.Address + Addresses.inUseOffset ) != 0;
                this.BoundStatus = Memory.ReadByte( this.Address + Addresses.boundStatusOffset );
                this.Bound = !Helpers.BitAnd( this.BoundStatus, 1 );
                this.RequiredLvl = Memory.ReadInt( this.BaseItemAddress + Addresses.requiredLevelOffset );
                this.MaxStack = Memory.ReadInt( this.BaseItemAddress + Addresses.maxStackOffset );
                this.ObjType = Memory.ReadInt( this.BaseItemAddress + Addresses.typeOffset );
                this.ObjSubType = Memory.ReadInt( this.BaseItemAddress + Addresses.typeOffset + 4 );
                this.ObjSubSubType = Memory.ReadInt( this.BaseItemAddress + Addresses.typeOffset + 8 );

                this.CoolDownTime = 0;
                if ( this.ObjType == 2 )
                { // Consumables, lets try to get CD time
                    var skillItemId = Memory.ReadInt( this.BaseItemAddress + Addresses.realItemIdOffset );
                    if ( skillItemId != 0 )
                    {
                        var skillItemAddress = MemoryTable.GetItemAddress( skillItemId );
                        if ( skillItemAddress != 0 )
                        {
                            this.CoolDownTime = Memory.ReadInt( skillItemAddress + Addresses.coolDownOffset );
                        }
                    }
                    // //cprintf( cli.yellow, "Cool down for consumable: %d\n", this.CoolDownTime );
                }
                int tmp;
                // Special case for cards
                if ( this.Id >= 770000 && this.Id <= 772000 )
                {
                    // We need to get info from NPC...
                    tmp = Memory.ReadInt( this.BaseItemAddress + Addresses.idCardNPCOffset );
                    var npcInfoAddress = MemoryTable.GetItemAddress( tmp );
                    if ( npcInfoAddress != 0 )
                    {
                        nameAddress = ( int ) Memory.ReadUInt( npcInfoAddress + Addresses.nameOffset );
                    }
                    else
                    {
                        Bot.Printf( System.Drawing.Color.Red, "Failed to get Address for NPC Id {0}", tmp );
                    }
                    this.Name = Resources.Card; // We should add a string so we can localize this
                }
                else if ( this.Id >= 550000 && this.Id <= 552000 )
                {
                    // We need to get info from item...
                    tmp = Memory.ReadInt( this.BaseItemAddress + Addresses.idRecipeItemOffset );
                    var itemInfoAddress = MemoryTable.GetItemAddress( tmp );
                    if ( itemInfoAddress != 0 )
                    {
                        nameAddress = ( int ) Memory.ReadUInt( itemInfoAddress + Addresses.nameOffset );
                    }
                    else
                    {
                        Bot.Printf( System.Drawing.Color.Red, "Failed to get Address for NPC Id {0}", tmp );
                    }
                    this.Name = Resources.Recipe;
                }
                else
                {
                    nameAddress = ( int ) Memory.ReadUInt( this.BaseItemAddress + Addresses.nameOffset );
                }
                this.Name = nameAddress != 0 ? string.Format( this.Name, Memory.ReadUtf8String( nameAddress ) ) : string.Format( this.Name, EmptyName );

                this.Quality = Memory.ReadInt( this.BaseItemAddress + Addresses.qualityBaseOffset );
                var num1 = ( int ) Math.Truncate( Memory.ReadByte( this.Address + Addresses.qualityTierOffset ) / new Decimal( 16 ) );
                if ( num1 > 0 )
                {
                    this.Quality = this.Quality + num1 / 2;
                }

                // Assign color based on quality
                this.Color = ( int ) Itemcolor[ this.Quality ];

                // Build an usable ItemLink
                this.ItemLink = string.Format( ItemLinkFormat, this.Id.ToString( "x8" ), this.Color.ToString( "x8" ), this.Name );

                // Get Stats (only named stats) and runes
                this.Stats.Clear();
                this.Runes.Clear();
                if ( this.ObjType == 0 || this.ObjType == 1 || this.ObjType == 5 )
                { // Weapons, Armor and Equipment Enhancements, runes
                    for ( var i = 0; i < 6; i++ )
                    {
                        var tmpid = ( int ) Memory.ReadUShort( this.Address + Addresses.itemStatsOffset + 0x2 * i );
                        string tmpname;
                        if ( tmpid != 0 )
                        {
                            tmpid = tmpid + 500000;
                            tmpname = MemoryTable.GetIdName( tmpid );
                            if ( !string.IsNullOrEmpty( tmpname ) )
                            {
                                var nameAndLevel = Helpers.ParseRomanNumberInName( tmpname );
                                this.Stats.Add( new ItemEffect { Id = tmpid, Name = tmpname, Level = nameAndLevel.Value } );
                            }
                        }

                        if ( i >= 4 || ( tmpid = Memory.ReadUShort( this.Address + Addresses.itemStatsOffset + 0xC + ( 0x2 * i ) ) ) == 0 )
                        {
                            continue;
                        }
                        //tmpid = Memory.ReadUShort( this.Address + Addresses.itemStatsOffset + 0xC + ( 0x2 * i ) );
                        //if ( tmpid == 0 )
                        //{ // No more runes
                        //    continue;
                        //}
                        tmpid = tmpid + 500000;
                        tmpname = MemoryTable.GetIdName( tmpid );
                        if ( !string.IsNullOrEmpty( tmpname ) )
                        {
                            var nameAndLevel = Helpers.ParseRomanNumberInName( tmpname );
                            this.Runes.Add( new ItemEffect { Id = tmpid, Name = tmpname, Level = nameAndLevel.Value } );
                        }
                    }
                }

                // Get base item flag values
                var flags = Memory.ReadInt( this.BaseItemAddress + Addresses.itemFlagsOffset );
                this.ItemShopItem = Helpers.BitAnd( flags, ItemFlagItemshopitemMask );
                this.CanBeSold = Helpers.BitAnd( flags, ItemFlagCanbesoldMask );
            }
            else if ( this.Id == 0 )
            {
                this.Empty = true;
                this.Id = 0;
                this.Name = EmptyName;
                this.ItemCount = 0;
                this.Color = 0xffffff;
                this.Icon = string.Empty;
                this.ItemLink = EmptyItemLink;
                this.Durability = 0;
                this.Quality = 0; // 0 = white / 1 = green / 2 = blue / 3 = purple / 4 = orange / 5 = gold
                this.Value = 0;
                this.Worth = 0;
                this.InUse = false;
                this.RequiredLvl = 0;
                this.Stats.Clear();
                this.Runes.Clear();
            }
            else
            {
                // if id is not 0 and hasn't changed we only update these values
                this.ItemCount = Memory.ReadInt( this.Address + Addresses.itemCountOffset );
                this.Durability = Memory.ReadInt( this.Address + Addresses.durabilityOffset );
                if ( this.Durability > 0 )
                {
                    this.Durability = this.Durability / 100;
                }
                this.InUse = Memory.ReadInt( this.Address + Addresses.inUseOffset ) != 0;
                this.BoundStatus = Memory.ReadInt( this.Address + Addresses.boundStatusOffset );
                this.Bound = !Helpers.BitAnd( this.BoundStatus, 1 );
            }
        }

        public ItemType Type
        {
            get { return ( ItemType ) this.ObjType; }
        }

        public int Level { get; set; }

        public bool CanBeSold { get; set; }

        public bool ItemShopItem { get; set; }

        public int ObjSubSubType { get; set; }

        public int ObjSubType { get; set; }

        public float MaxDurability { get; set; }

        public bool Bound { get; set; }

        public void Delete()
        {
            if ( this.Available && !this.Empty )
            {
                // Special case for bank. Check if it's open
                if ( this.Location == "bank" )
                {
                    var bankClosed = Memory.ReadIntPtr( Addresses.bankOpenPtr, Addresses.bankOpen_offset ) == -1;
                    if ( bankClosed )
                    {
                        return;
                    }
                }

                this.Pickup();

                if ( Helpers.RoMScript<bool>( "CursorHasItem()" ) )
                {
                    Helpers.RoMScript( "DeleteCursorItem()" );
                }

                this.Update();
            }
        }

        public void Pickup()
        {
            // FACTS ABOUT PICKING UP ITEMS
            //
            // 1. The moment you pick up an item, the slot becomes 'InUse' && remains so until it can be used again.
            // 2. The moment you pick up an item, the cursor has an item - 'Cursor.HasItem'.
            // 3. The moment you put an item down, the cursor wont have an item.
            // 4. The moment you put an item down, the 'to' slot wont neccessarily be 'InUse' yet. That's why I added the LastMovedTime to it so it doesn't get used again right away.

            this.Update();

            // Not rented || closed
            if ( !this.Available )
            {
                return;
            }

            // Special case for bank. Check if it's open
            if ( this.Location == "bank" )
            {
                var bankClosed = Memory.ReadIntPtr( Addresses.bankOpenPtr, Addresses.bankOpen_offset ) == -1;
                if ( bankClosed )
                {
                    return;
                }
            }

            if ( this.InUse && Cursor.HasItem && Cursor.ItemLocationName == this.Location && Cursor.ItemBagId == this.BagId )
            {
                // Returning item. No waiting.
                return;
            }
            if ( this.InUse )
            {
                // Wait till ! in use
                var starttime = DateTime.Now;
                do
                {
                    Thread.CurrentThread.Join( 50 );
                    this.UpdateInUse();
                } while ( this.InUse && ( DateTime.Now - starttime ).Seconds < ItemReuseDelay );

            }
            else if ( ( DateTime.Now - this.LastMovedTime ).Seconds < ItemReuseDelay )
            {
                // Wait delay time. Break if becomes locked
                do
                {
                    Thread.CurrentThread.Join( 50 );
                    this.UpdateInUse();
                } while ( this.InUse && ( DateTime.Now - this.LastMovedTime ).Seconds < ItemReuseDelay );

                // Wait until it is no longer locked.
                if ( this.InUse )
                {
                    do
                    {
                        Thread.CurrentThread.Join( 50 );
                        this.UpdateInUse();
                    } while ( this.InUse && ( DateTime.Now - this.LastMovedTime ).Seconds < ItemReuseDelay );
                }
            }
        }

        protected void UpdateInUse()
        {
            if ( this.Address == 0 )
            {
                return;
            }
            this.InUse = Memory.ReadInt( this.Address + Addresses.inUseOffset ) != 0;
        }

        public int GetRemainingCooldown()
        {
            var skillItemId = Memory.ReadInt( this.BaseItemAddress + Addresses.realItemIdOffset );
            if ( skillItemId != 0 )
            {
                var skillItemAddress = MemoryTable.GetItemAddress( skillItemId );
                if ( skillItemAddress != 0 )
                {
                    var val = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, skillItemAddress + 0xE0 );
                    int plusoffset;
                    switch ( val )
                    {
                        case 1:
                            plusoffset = 0;
                            break;
                        case 3:
                            plusoffset = 0x80C;
                            break;
                        default:
                            return -1;
                    }
                    if ( Memory.ReadRepeat<int>( Bot.Instancia.ProcId, skillItemAddress + 0xE0 ) != 0 )
                    {
                        var offset = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, skillItemAddress + Addresses.skillRemainingCooldown_offset );
                        return Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.staticCooldownsBase + plusoffset + ( offset + 1 ) * 4 ) / 10;
                    }
                }
            }
            return -1;
        }

        public bool IsType( string typeName )
        {
            if ( string.IsNullOrEmpty( typeName ) )
            {
                return false;
            }
            return typeName.ToLowerInvariant() == ( this.TypeNames.TypeName ?? "" ).ToLowerInvariant() ||
                   typeName.ToLowerInvariant() == ( this.TypeNames.SubTypeName ?? "" ).ToLowerInvariant() ||
                   typeName.ToLowerInvariant() == ( this.TypeNames.SubSubTypeName ?? "" ).ToLowerInvariant() ||
                   typeName.ToLowerInvariant() == ( this.TypeNames.SubSubUniqueTypeName ?? "" ).ToLowerInvariant();
        }

        public DateTime LastMovedTime { get; set; }

        public int Address { get; set; }

        public int BaseItemAddress { get; set; }

        public bool Empty { get; set; }

        public int Id { get; set; }

        public int BagId { get; set; }

        public string Name { get; set; }

        public int ItemCount { get; set; }

        public int Color { get; set; }

        public int SlotNumber { get; set; }

        public string Icon { get; set; }

        public string ItemLink { get; set; }

        public float Durability { get; set; }

        public int Quality { get; set; }

        public int Value { get; set; }

        public int Worth { get; set; }

        public bool InUse { get; set; }

        public int BoundStatus { get; set; }

        public int RequiredLvl { get; set; }

        public int CoolDownTime { get; set; }

        public DateTime LastTimeUsed { get; set; }

        public int MaxStack { get; set; }

        public int ObjType { get; set; }

        public bool Available { get; set; }

        public string Location { get; set; }

        public List<ItemEffect> Stats { get; set; }

        public List<ItemEffect> Runes { get; set; }

        public ItemTypesNames TypeNames
        {
            get
            {
                var itemType = Database.Database.ItemTypes.ContainsKey( this.ObjType ) ? Database.Database.ItemTypes[ this.ObjType ] : null;
                if ( itemType == null )
                {
                    return new ItemTypesNames();
                }

                var subSubType = itemType.Items.ContainsKey( this.ObjSubType ) && itemType.Items[ this.ObjSubType ].Items.ContainsKey( this.ObjSubSubType ) ? itemType.Items[ this.ObjSubType ].Items[ this.ObjSubSubType ] : null;
                var typeNames = new ItemTypesNames
                                {
                                    TypeName = itemType.Name,
                                    SubTypeName = itemType.Items.ContainsKey( this.ObjSubType ) ? itemType.Items[ this.ObjSubType ].Name : null
                                };

                if ( subSubType != null )
                {
                    typeNames.SubSubTypeName = subSubType.Name;
                    typeNames.SubSubUniqueTypeName = subSubType.UniqueName;
                }

                return typeNames;
            }
        }

        public override string ToString()
        {
            return string.Format( "Id: {0}, Name: {1}, Type: {2}\nTypeNames: {3}\nLink: {4}, Stats: {5}\nLevel: {6}\nRunes: {7}", Id, Name, ObjType, TypeNames, ItemLink, string.Join( ", ", Stats.Select( s => string.Format( "Id: {0}, Name: {1}", s.Id, s.Name ) ) ), this.RequiredLvl, string.Join( ", ", Runes.Select( s => string.Format( "Id: {0}, Name: {1}", s.Id, s.Name ) ) ) );
        }
    }
}